﻿#define CREATE_LEVEL

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JGLib.States;
using Microsoft.Xna.Framework.Graphics;
using TrafficTrouble.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input.Touch;

namespace TrafficTrouble.States
{
    public class GameState : State
    {
        public Grid Level { get; set; }

        private int GridSquareSize { get; set; }
        private Vector2 GridTopLeft { get; set; }

        private Car CarDragged;
        private Vector2 CarDragOffset;
        private HistoryItem CarDragMove;

#if DEBUG
        Vector2 touchPos;
        bool debugging = false;
#endif

        public GameState()
            : base(typeof(GameState))
        {
            CarDragged = null;
            History.Reset();
        }

        protected override void LoadContent()
        {
            this.LoadTexture("Game/Car_2", 2);
            this.LoadTexture("Game/Car_Player_2", -2);
#if DEBUG
            this.LoadContent<SpriteFont>("MainMenu/MenuFont", true);
#endif
        }

        protected override void GetSwitchedTo(object prevStateID, object[] data)
        {
            if ((Type)prevStateID == typeof(LevelSelectionState))
            {
#if !CREATE_LEVEL
                this.Level = (Grid)data[0];
#else
                this.Level = Util.GetLevelFromFile("Level1");
                this.GridSquareSize = Game.Width / this.Level.Width;
                this.GridTopLeft = new Vector2(0, (Game.Height - this.Level.Height * this.GridSquareSize) / 2);
                string str = this.Level.ToString();
#endif
            }
        }

        #region Updating
        protected override void Update(double gameTime)
        {
            foreach (TouchLocation touch in Touch.Touches)
            {
#if DEBUG
                touchPos = touch.Position;
                if (touchPos.X < this.GridSquareSize / 2 && touchPos.Y < this.GridSquareSize / 2)
                    debugging ^= true;
#endif
                if (this.CarDragged == null)
                    this.checkStartCarDrag(touch);
                else
                {
                    if (touch.State == TouchLocationState.Released)
                        this.releaseCar();
                    else if (touch.State == TouchLocationState.Moved)
                        slideCar(touch);
                }
            }

            base.Update(gameTime);
        }

        private void checkStartCarDrag(TouchLocation touch)
        {
            if (touch.State == TouchLocationState.Pressed)
            {
                Point touchLoc = this.GetGridCoords(touch.Position);
                Car car = this.Level.GetCarAt(touchLoc.X, touchLoc.Y);
                if (car != null) // if the user did NOT touch an empty space
                {
                    this.CarDragged = car;
                    this.CarDragOffset = touch.Position - this.GridTopLeft
                        - new Vector2(CarDragged.X, CarDragged.Y) * this.GridSquareSize;

                    this.CarDragMove = new HistoryItem(this.CarDragged, this.CarDragged.GridCoords, Point.Zero);
                }
            }
        }
        private void releaseCar()
        {
            // stop moving car, add move to history if its displacement was not zero,
            // and reset car-dragging variables.
            this.CarDragMove.End = this.CarDragged.GridCoords;
            if (this.CarDragMove.Start != this.CarDragMove.End)
                History.AddMove(this.CarDragMove);
            this.CarDragged = null;
        }
        private void slideCar(TouchLocation touch)
        {
            // basically just move the car as far as it can without hitting another car.
            Point newCarLoc = this.GetGridCoordsFromClick(touch.Position),
                  oldCarLoc = new Point(this.CarDragged.X, this.CarDragged.Y);
            if (this.CarDragged.Horizontal)
            {
                // check every spot to avoid cars 'jumping' other cars.
                while (oldCarLoc.X != newCarLoc.X)
                {
                    if (newCarLoc.X > oldCarLoc.X) oldCarLoc.X++;
                    else oldCarLoc.X--;
                    if (this.CarDragged.CanBeMovedTo(oldCarLoc.X, this.CarDragged.Y))
                        this.CarDragged.X = newCarLoc.X;
                    else break;
                }
            }
            else
            {
                // check every spot to avoid cars 'jumping' other cars.
                while (oldCarLoc.Y != newCarLoc.Y)
                {
                    if (newCarLoc.Y > oldCarLoc.Y) oldCarLoc.Y++;
                    else oldCarLoc.Y--;
                    if (this.CarDragged.CanBeMovedTo(this.CarDragged.X, oldCarLoc.Y))
                        this.CarDragged.Y = newCarLoc.Y;
                    else break;
                }
            }
        }
        #endregion

        #region Non-Car-Sliding Functionality (Restart, Undo, Hint, Menu)
        private void RestartLevel()
        {
            this.CarDragged = null;
            this.Level = Util.GetLevelFromFile(this.Level.Name);
            History.Reset();
        }
        private void UndoMove()
        {
            if (!History.HasHistory) return;
            HistoryItem lastMove = History.GetLastMove();

            if (lastMove.End != lastMove.Car.GridCoords)
                throw new Exception("WTF? (TrafficTrouble.States.GameState.UndoMove()).");

            lastMove.Car.GridCoords = lastMove.Start;
        }
        private HistoryItem GetHint()
        {

            // TODO - write A* search algorithm for best solution and return first move of that solution.
            throw new NotImplementedException();
        }
        private void SwitchToMenu()
        {

        }
        #endregion

        #region Drawing
        protected override void Draw(SpriteBatch g)
        {
#if DEBUG
            if (debugging)
                DrawGrid(g);
#endif
            this.DrawCars(g);
#if DEBUG
            if (debugging)
            {
                Point coord = this.GetGridCoords(this.touchPos);
                g.DrawString(this.GetContent<SpriteFont>(true),
                    "(" + coord.X + "," + coord.Y + "):"
                    + (Level.IsEmpty(coord.X, coord.Y) ? "Empty" : ("[" + Level.GetCarAt(coord.X, coord.Y).ToString() + "]")),
                    touchPos, Color.White * .5f);
            }
#endif
        }

#if DEBUG
        private void DrawGrid(SpriteBatch g)
        {
            Texture2D line = new Texture2D(Game.TrafficTroubleGame.GraphicsDevice, 1, 1);
            line.SetData<Color>(new Color[] { new Color(255, 255, 255, 255 / 2) });

            for (int x = 0; x <= this.Level.Width; x++)
                g.Draw(line, new Rectangle((int)this.GridTopLeft.X + x * this.GridSquareSize, (int)this.GridTopLeft.Y,
                    1, this.Level.Height * this.GridSquareSize), Color.White);
            // the right-most column will be one pixel smaller x-wise because the next line draws on its right edge,
            // and previously a line was drawn on its left edge as well.
            g.Draw(line, new Rectangle((int)this.GridTopLeft.X + this.Level.Width * this.GridSquareSize - 1, (int)this.GridTopLeft.Y,
                1, this.Level.Height * this.GridSquareSize), Color.White);
            for (int y = 0; y <= this.Level.Height; y++)
                g.Draw(line, new Rectangle((int)this.GridTopLeft.X, (int)this.GridTopLeft.Y + y * this.GridSquareSize,
                    this.Level.Width * this.GridSquareSize, 1), Color.White);
        }
#endif
        private void DrawCars(SpriteBatch g)
        {
            foreach (Car car in this.Level.Cars)
            {
                Texture2D carTex = this.GetTexture(2 * (car.PlayerCar ? -1 : 1));
                //this.GetTexture(car.Length * (car.PlayerCar ? -1 : 1)); // <-- uncomment this line, delete this comment, and delete the previous one once more car images are found.
                Vector2 squareTopLeft = this.GetGridSquareTopLeft(car.GridCoords);
                if (car.Horizontal)
                {
                    g.Draw(carTex, new Rectangle((int)squareTopLeft.X, (int)squareTopLeft.Y,
                            car.Length * this.GridSquareSize, this.GridSquareSize),
                        Color.White);
                }
                else
                {
                    // draw rotated 90 degrees to the right. (left?... whatever)
                    g.Draw(carTex, new Rectangle((int)squareTopLeft.X, (int)squareTopLeft.Y,
                            car.Length * this.GridSquareSize, this.GridSquareSize),
                        null,
                        Color.White,
                        MathHelper.PiOver2,
                        new Vector2(0, carTex.Height),
                        SpriteEffects.None,
                        0);
                }
            }
        }
        #endregion

        #region Utility Methods
        /// <summary>Gets the coordinates of the grid square of the pixel at the specified location on the screen.</summary>
        /// <param name="location">The location on the screen, in pixels.</param>
        /// <returns>The coordinates of the grid square.</returns>
        private Point GetGridCoords(Vector2 location)
        {
            Vector2 gridLoc = location - this.GridTopLeft;
            return new Point((int)(gridLoc.X / this.GridSquareSize),
                             (int)(gridLoc.Y / this.GridSquareSize));
        }
        /// <summary>Gets the coordinates of the grid square under a touch location, compensated for car drag offset.</summary>
        /// <param name="click">The location of the touch.</param>
        /// <returns>The coordinates of the grid square.</returns>
        private Point GetGridCoordsFromClick(Vector2 click)
        {
            return GetGridCoords(click - this.CarDragOffset);
        }

        /// <summary>Gets the location of the top-left corner of the specified grid square.</summary>
        /// <param name="gridCoords">The coordinates of the grid square.</param>
        /// <returns>The location of the top-left corner, in pixels.</returns>
        private Vector2 GetGridSquareTopLeft(Point gridCoords)
        {
            return new Vector2(this.GridTopLeft.X + gridCoords.X * this.GridSquareSize,
                               this.GridTopLeft.Y + gridCoords.Y * this.GridSquareSize);
        }
        #endregion
    }
}
